﻿/**
 * The AudioTrack Library by Chris J. Rock. Aug 10, 2011
 * Check out chrisjrock.net and be a king among men.
 *
 * Copyright (c) 2011 Chris J. Rock
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 **/


package net.chrisjrock.sound.audioTrack.track 
{
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.media.ID3Info;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.media.SoundLoaderContext;
	import flash.media.SoundTransform;
	import flash.net.URLRequest;
	
	import net.chrisjrock.sound.audioTrack.data.AudioTrackData;
	import net.chrisjrock.sound.audioTrack.data.IAudioTrackData;
	import net.chrisjrock.sound.audioTrack.events.AudioTrackEvent;
	
	/**
	 * The standard sound management class meant for use with Flash's Sound object.
	 * @author Christopher J. Rock
	 */
	
	public class AudioTrack extends AbstractTrack implements ISequencedTrack 
	{
		private var _input:Sound;
		private var _loading:Boolean;
		
		private var _inputChannel:SoundChannel;
		private var _position:Number;
		private var _playing:Boolean;
		
		private var _autoPlay:Boolean;
		private var _startPos:Number;
		//private var _endTime:Number;
		private var _duration:Number;
		private var _loops:int;
		private var _loopInfinitely:Boolean;
		private var _loopUpdate:Boolean;
		
		private var _soundContext:SoundLoaderContext;
		
		public function AudioTrack(trackId:String , input:Sound = null , autoPlay:Boolean = false) 
		{
			super(trackId);
			
			_position = _startPos = 0;
			
			_autoPlay = autoPlay;
			if (input)
			{
				setInput(input);
			}
		}
		
		/**
		 * @inheritDoc
		 */
		override public function dispose():void 
		{
			stop();
			abortLoad();
			_input = null;
			_duration = NaN;
			super.dispose();
		}
		
		/**
		 * Sets the source Sound object. 
		 * @param input
		 * 
		 */		
		public function setInput (input:Sound = null):void 
		{
			stop();
			abortLoad();
			_input = input;
			if (_input) 
			{
				_duration = _input.length;
				if (isNaN(_startPos))
				{
					_position = 0;
				}
				else
				{
					_position = _startPos;
				}
				
				if (_autoPlay && input.bytesTotal)
				{
					play();
				}
			}
		}
		
		/**
		 * @inheritDoc
		 */
		public function load (url:String):void 
		{
			stop();
			abortLoad();
			_duration = NaN;
			if (!_input)
			{
				setInput(new Sound());
			}
			_loading = true;
			_input.addEventListener(Event.COMPLETE , loadCompleteHandler);
			_input.addEventListener(IOErrorEvent.IO_ERROR , ioErrorHandler);
			_input.addEventListener(ProgressEvent.PROGRESS , progressHandler);
			_input.load(new URLRequest(url) , _soundContext);
		}
		
		/**
		 * Aborts loading for the input Sound object if loading is in progress.
		 * 
		 */		
		public function abortLoad ():void 
		{
			if (_loading) 
			{
				_loading = false;
				_input.removeEventListener(ProgressEvent.PROGRESS , progressHandler);
				_input.removeEventListener(Event.COMPLETE , loadCompleteHandler);
				_input.removeEventListener(IOErrorEvent.IO_ERROR , ioErrorHandler);
				_input.close();
			}
		}
		
		/**
		 * @inheritDoc
		 */
		public function stop():void 
		{
			if (_playing) 
			{
				_inputChannel.stop();
				_inputChannel.removeEventListener(Event.SOUND_COMPLETE , soundCompleteHandler);
				_playing = false;
			}
			
			if (_position != _startPos)
			{
				_position = _startPos;
				dispatchEvent(new AudioTrackEvent(AudioTrackEvent.AT_START));
				dispatchEvent(new AudioTrackEvent(AudioTrackEvent.STOP));
			}
		}
		
		/**
		 * @inheritDoc
		 */
		public function play (restart:Boolean = false):void 
		{
			if (_input && (restart || _loopUpdate || !_playing)) 
			{
				if (_inputChannel)
				{
					_inputChannel.stop();
				}
				
				if (restart && !_loopUpdate || _position >= _duration)
				{
					_position = _startPos;
				}
				
				_inputChannel = _input.play(_position , _loops , getTransform());
				if (!_inputChannel)
				{
					return ;
				}
				
				_inputChannel.addEventListener(Event.SOUND_COMPLETE , soundCompleteHandler);
				_playing = true;
				
				if (!_loopUpdate) 
				{
					dispatchEvent(new AudioTrackEvent(AudioTrackEvent.PLAY));
					if (_inputChannel.position == _startPos)
					{
						dispatchEvent(new AudioTrackEvent(AudioTrackEvent.LEAVE_START));
					}
				}
				else
				{
					_loopUpdate = false;
				}
				_position = 0;
			}
		}
		
		/**
		 * @inheritDoc
		 */
		public function pause():void 
		{
			if (_playing) 
			{
				_playing = false;
				_position = _inputChannel.position;
				_inputChannel.stop();
				dispatchEvent(new AudioTrackEvent(AudioTrackEvent.PAUSE));
			}
		}
		
		/**
		 * @inheritDoc
		 */
		public function scrubTo (position:Number):void 
		{
			var leaveStart:Boolean = _position == _startPos;
			var leaveEnd:Boolean = _position == _duration;
			
			if (_playing)
			{
				_inputChannel.stop();
				_inputChannel = _input.play(position , _loops , getTransform());
				updateTransform();
			}
			
			if (leaveStart)
			{
				dispatchEvent(new AudioTrackEvent(AudioTrackEvent.LEAVE_START));
			}
			else if (position == _startPos)
			{
				dispatchEvent(new AudioTrackEvent(AudioTrackEvent.AT_START));
			}
			
			if (leaveEnd)
			{
				dispatchEvent(new AudioTrackEvent(AudioTrackEvent.LEAVE_END));
			}
			else if (position == _duration)
			{
				dispatchEvent(new AudioTrackEvent(AudioTrackEvent.AT_END));
			}
		}
		
		/**
		 * @inheritDoc
		 */
		override public function updateTransform (transform:SoundTransform = null /*, panRule:Number = NaN*/):void 
		{
			super.updateTransform(transform);
			
			if (_inputChannel)
			{
				_inputChannel.soundTransform = getTransform();
			}
		}
		
		/**
		 * @inheritDoc
		 */
		override protected function instantiateTrackData ():IAudioTrackData
		{
			return new AudioTrackData();
		}
		
		/**
		 * @inheritDoc
		 */
		override protected function applyEffect ():void
		{
			updateTransform();
		}
		
		private function progressHandler (event:ProgressEvent):void 
		{
			dispatchEvent(new AudioTrackEvent(AudioTrackEvent.PROGRESS));
		}
		
		private function loadCompleteHandler (event:Event):void 
		{
			_input.removeEventListener(ProgressEvent.PROGRESS , progressHandler);
			_input.removeEventListener(Event.COMPLETE , loadCompleteHandler);
			_input.removeEventListener(IOErrorEvent.IO_ERROR , ioErrorHandler);
			_duration = _input.length;
			_loading = false;
			dispatchEvent(new AudioTrackEvent(AudioTrackEvent.LOAD_COMPLETE));
			if (_autoPlay)
			{
				play();
			}
		}
		
		private function ioErrorHandler (event:IOErrorEvent):void 
		{
			_input.removeEventListener(ProgressEvent.PROGRESS , progressHandler);
			_input.removeEventListener(Event.COMPLETE , loadCompleteHandler);
			_input.removeEventListener(IOErrorEvent.IO_ERROR , ioErrorHandler);
			dispatchEvent(new IOErrorEvent(AudioTrackEvent.IO_ERROR , false , false , "AudioTrack: " + event.text));
		}
		
		private function soundCompleteHandler (event:Event):void 
		{
			_inputChannel.removeEventListener(Event.SOUND_COMPLETE , soundCompleteHandler);
			if (_loopInfinitely)
			{
				play(true);
			}
			else 
			{
				_playing = false;
				_position = _duration;
				dispatchEvent(new AudioTrackEvent(AudioTrackEvent.AT_END));
			}
		}
		
		
		/**
		 * The SoundLoaderContext object used for loading within this track. 
		 * @return 
		 * 
		 */		
		public function get soundContext ():SoundLoaderContext
		{
			return _soundContext;
		}
		
		public function set soundContext (value:SoundLoaderContext):void
		{
			_soundContext = value;
		}
		
		/**
		 * Whether or not the track is currently loading. 
		 * @return 
		 * 
		 */		
		public function get loading ():Boolean
		{
			return _loading;
		}
		
		
		/**
		 * How many times the track will be looped.
		 * @return 
		 * 
		 */		
		public function get loops ():int
		{
			return _loops;
		}
		
		public function set loops (value:int):void 
		{
			if (_loops != value || _loopInfinitely) 
			{
				_loops = value;
				_loopInfinitely = false;
				_loopUpdate = true;
				if (_playing)
				{
					play();
				}
			}
		}
		
		/**
		 * Forces the track to loop infinitely. 
		 * @return 
		 * 
		 */		
		public function get loopInfinitely ():Boolean
		{
			return _loopInfinitely;
		}
		
		public function set loopInfinitely (value:Boolean):void 
		{
			if (_loopInfinitely != value) 
			{
				_loopInfinitely = value;
				if (_loopInfinitely)
				{
					_loops = 0;
				}
				_loopUpdate = true;
				if (_playing)
				{
					play();
				}
			}
		}
		
		/**
		 * The bytesLoaded value of the input Sound object. 
		 * @return 
		 * 
		 */		
		public function get bytesLoaded ():uint
		{
			return _input.bytesLoaded;
		}
		
		/**
		 * The bytesTotal value of the input Sound object. 
		 * @return 
		 * 
		 */		
		public function get bytesTotal ():int
		{
			return _input.bytesTotal;
		}
		
		/**
		 * The id3 value of the input Sound object. 
		 * @return 
		 * 
		 */		
		public function get id3 ():ID3Info
		{
			return _input.id3;
		}
		
		/**
		 * The isBuffering value of the input Sound object. 
		 * @return 
		 * 
		 */		
		public function get isBuffering ():Boolean
		{
			return _input.isBuffering;
		}
		
		/**
		 * The current sequence position of the track's playhead.
		 */
		public function get position ():Number
		{
			if (_playing)
			{
				return _inputChannel.position;
			}
			
			return _position;
		}
		
		/**
		 * @inheritDoc
		 */
		public function get duration ():Number
		{
			return _duration;
		}
		
		/**
		 * The url value of the input Sound object. 
		 * @return 
		 * 
		 */		
		public function get url ():String
		{
			return _input.url;
		}
		
		
		/**
		 * The leftPeak value of the input SoundChannel object, if the sound is currently playing. If it is not playing, NaN is returned.
		 * @return 
		 * 
		 */		
		public function get leftPeak ():Number
		{
			if (_inputChannel)
			{
				return _inputChannel.leftPeak;
			}
			return NaN;
		}
		
		/**
		 * The rightPeak value of the input SoundChannel object, if the sound is currently playing. If it is not playing, NaN is returned.
		 * @return 
		 * 
		 */		
		public function get rightPeak ():Number
		{
			if (_inputChannel)
			{
				return _inputChannel.rightPeak;
			}
			return NaN;
		}
		
		/**
		 * Returns the source Sound object.
		 */		
		public function get input ():Sound
		{
			return _input;
		}
		
		/**
		 * @inheritDoc
		 */
		public function get playing ():Boolean
		{
			return _playing;
		}
		
		/**
		 * @inheritDoc
		 */
		public function get paused ():Boolean
		{
			return !_playing && _position != _startPos;
		}
		
		/**
		 * Determines whether or not a track will play as soon as the input data is available.
		 * @return 
		 */		
		public function get autoPlay():Boolean
		{
			return _autoPlay;
		}
		
		/**
		 * @private
		 */
		public function set autoPlay(value:Boolean):void
		{
			_autoPlay = value;
		}
		
		/**
		 * Determines the position at which the track will start when it plays from the beginning.
		 */
		public function get startPos():Number
		{
			return _startPos;
		}
		
		/**
		 * @private
		 */
		public function set startPos(value:Number):void
		{
			_startPos = value;
		}

	}
	
}